#include "kvalloc.h"

#include "gtest/gtest.h"

using namespace kvalloc;

class single_thread_test : public testing::Test {
protected:
    KVAlloc * allocator;
    FileAddr kvs[SIZECLASS_NUM];

public:
    virtual void SetUp() {
        if(file_exist("/home/lyp/dnskv/build/test.db"))
            remove("/home/lyp/dnskv/build/test.db");
        if(file_exist("/mnt/pmem/testmeta.pool"))
            remove("/mnt/pmem/testmeta.pool");
        allocator = new KVAlloc("/home/lyp/dnskv/build/test.db","/mnt/pmem/testmeta.pool", 1024);
        for(auto iter : SIZECLASS) {
            kvs[iter.second] = allocator->Alloc(iter.first);
        }
    }

    virtual void TearDown() {
        delete allocator;
    }
};

TEST_F(single_thread_test, alloc) {
    // use one size to allocate some kv pairs and surpass one slab capacity
    constexpr int ALLOC_SIZE = 60;
    auto iter = SIZECLASS.lower_bound(ALLOC_SIZE);
    int TEST_NUM = SLAB_SIZE / iter->first;

    FileAddr tmp[TEST_NUM];
    for(int i = 0; i < TEST_NUM - 1; i++) {
        tmp[i] = allocator->Alloc(ALLOC_SIZE);
        ASSERT_EQ(tmp[i].slabid, kvs[iter->second].slabid);
        ASSERT_EQ(tmp[i].slaboff, (i + 1) * iter->first);
    }
    tmp[TEST_NUM - 1] = allocator->Alloc(ALLOC_SIZE);
    ASSERT_NE(tmp[TEST_NUM - 1].slabid, kvs[iter->second].slabid);

    for(int i = 0; i < TEST_NUM; i++) {
        allocator->Free(tmp[i]);
    }
}

TEST_F(single_thread_test, free) {
    constexpr int ALLOC_SIZE = 60;
    auto iter = SIZECLASS.lower_bound(ALLOC_SIZE);
    int TEST_NUM = SLAB_SIZE / iter->first - 1;

    // allocate one slab till full
    FileAddr tmp[TEST_NUM];
    for(int i = 0; i < TEST_NUM; i++) {
        tmp[i] = allocator->Alloc(ALLOC_SIZE);
    }
    ASSERT_EQ(allocator->at(tmp[0].slabid)->count_.load(), TEST_NUM + 1);
    FileAddr test = allocator->Alloc(ALLOC_SIZE);
    ASSERT_EQ(allocator->at(tmp[0].slabid)->state_, FULL);
    allocator->Free(test);

    // free all objects till empty
    for(int i = 0; i < TEST_NUM; i++) {
        allocator->Free(tmp[i]);
    }
    ASSERT_EQ(allocator->at(tmp[0].slabid)->count_.load(), 1);
}

TEST_F(single_thread_test, reuse) {
    constexpr int ALLOC_SIZE = 60;
    auto iter = SIZECLASS.lower_bound(ALLOC_SIZE);
    int TEST_NUM = SLAB_SIZE / iter->first;
    // each time add a slab into free list
    for(int k = 0; k < KVAlloc::ALLOC_BATCH; k++) {
        FileAddr tmp[TEST_NUM];
        for(int i = 0; i < TEST_NUM; i++) {
            tmp[i] = allocator->Alloc(ALLOC_SIZE);
        }
        FileAddr dummy; 
        if(k > 0) dummy = allocator->Alloc(ALLOC_SIZE);
        if(k > 0) allocator->Free(dummy);
        for(int i = 0; i < TEST_NUM; i++) {
            allocator->Free(tmp[i]);
        }
    }
    // currently we are reusing the free list
    FileAddr one = allocator->Alloc(ALLOC_SIZE);
    ASSERT_EQ(one.slabid, kvs[iter->second].slabid);
    ASSERT_EQ(allocator->at(one.slabid)->count_.load(), 2);
}